<?php
/**
 * Created by PhpStorm.
 * User: HouJie
 * Date: 2018-4-3
 * Time: 15:39
 */

namespace Bqdove\Cache;


use Bqdove\Contracts\CacheInterface;

class FileCache extends CacheInterface
{

    protected $directory;

    /**
     * FileCache constructor.
     * @param array $config
     * @throws \Exception
     */
    public function __construct(array $config)
    {
        if (!is_null($config)) {
            $this->config = array_merge($this->config, $config);
        }

        if (!isset($config['path'])) {
            throw new \Exception('The ' . __METHOD__ . ' engine configure item does not have a path node.');
        }

        $this->directory = $config['path'];

        if (!is_dir($this->directory)) {
            if (!@mkdir($this->directory, '0777', true)) {
                throw new \Exception('Not a directory');
            }

            if (!$this->is_writable($this->directory)) {
                throw new \Exception('The directory has no permissions to write.');
            }
        }
    }


    /**
     * 获取文件内容
     * @param string $key
     * @param null $default
     * @return mixed
     */
    public function get($key, $default = null)
    {
        $filename = $this->getFileName($key);
        if (!is_file($filename)) return $this->value($default);

        $content = file_get_contents($filename);
        if ($content !== false) {
            $minutes = (int)substr($content, 12);
            if ($minutes != 0 && $_SERVER['REQUEST_TIME'] > filemtime($filename) + $minutes * 60) {
                $this->unlink($filename);
                return $this->value($default);
            }

            $content = substr($content, 12);
            if ($this->config['is_zip']) {
                $content = gzuncompress($content);
            }
            return $this->unserialize($content);
        }
        return $this->value($default);
    }

    /**
     * 存储数据
     * @param string $key
     * @param mixed $value
     * @param null $ttl
     * @return bool
     */
    public function set($key, $value, $ttl = null)
    {
        if (is_null($ttl)) {
            $minutes = $this->config['expired'];
        }
        $filename = $this->getFileName($key);
        $data = $this->serialize($value);
        if ($this->config['is_zip']) {
            $data = gzcompress($data, $this->config['zip_level']);
        }
        $data = sprintf('%012d', $minutes) . $data;
        $result = file_put_contents($filename, $data);
        if ($result) {
            clearstatcache();
            return true;
        }
        return false;
    }

    /**
     * 删除指定的key
     * @param string $key
     * @return bool
     */
    public function delete($key)
    {
        if ($this->has($key)) {
            return $this->unlink($this->getFileName($key));
        }
        return false;
    }

    /**
     * 清除所有的缓存
     * @return bool
     */
    public function clear()
    {
        $this->removeDir($this->directory);
        @mkdir($this->directory, 0755, true);
        return true;
    }

    /**
     * 永久缓存
     * @param $key
     * @param $value
     * @return bool|mixed
     */
    public function forever($key, $value)
    {
        return $this->set($key, $value, 0);
    }

    /**
     * 删除并获取
     * @param $key
     * @return mixed
     */
    public function pull($key)
    {
        $value = $this->get($key);
        $this->delete($key);
        return $value;
    }

    /**
     * 自增
     * @param $key
     * @param int $value
     * @return bool|int|mixed
     */
    public function increment($key, $value = 1)
    {
        if ($this->has($key)) {
            $value = $this->get($key) + $value;
        }

        return $this->set($key, $value) === true ? $value : false;
    }

    /**
     * 自减
     * @param $key
     * @param int $value
     * @return bool|int|mixed
     */
    public function decrement($key, $value = 1)
    {
        if ($this->has($key)) {
            $value = $this->get($key) - $value;
        }

        return $this->set($key, $value) === true ? $value : false;
    }


    public function getMultiple($keys, $default = null)
    {
        // TODO: Implement getMultiple() method.
    }

    public function setMultiple($values, $ttl = null)
    {
        // TODO: Implement setMultiple() method.
    }

    public function deleteMultiple($keys)
    {
        // TODO: Implement deleteMultiple() method.
    }

    /**
     * 判断是否存在指定的key
     * @param string $key
     * @return bool
     */
    public function has($key)
    {
        return $this->get($key) ? true : false;
    }


    /**
     * 返回缓存的key
     * @param string $key
     * @return string
     */
    public function getKey(string $key): string
    {
        return $this->config['preFix'] . $key;
    }


    /**
     * 删除 文件
     * @param string $path
     * @return bool
     */
    protected function unlink(string $path): bool
    {
        return is_file($path) && unlink($path);
    }

    /**
     *  判断文件是否有可写的权限
     * @param string $file
     * @return bool
     */
    protected function is_writable(string $file): bool
    {
        if (is_dir($file)) {
            if ($fp = @fopen("$file/test.txt", 'w')) {
                @fclose($fp);
                $this->unlink($file . '\test.txt');
                return true;
            }
            return false;
        }

        if ($fp = @fopen($file, "a+")) {
            @fclose($fp);
            return true;
        }

        return false;
    }

    /**
     * 递归删除目录
     * @param string $dirName
     * @return bool
     */
    protected function removeDir(string $dirName): bool
    {
        if (!is_dir($dirName)) return false;

        $handle = @opendir($dirName);
        while (($file = @readdir($handle)) !== false) {
            if ($file != '.' && $file != '..') {
                $dir = $dirName . '/' . $file;
                is_dir($dir) ? $this->removeDir($dir) : $this->unlink($dir);
            }
        }

        closedir($handle);
        return is_dir($dirName) && rmdir($dirName);
    }


    /**
     * 返回缓存文件的名称
     * @param string $key
     * @return string
     */
    protected function getFileName(string $key): string
    {
        $name = md5($this->getKey($key));
        $filename = $this->directory . substr($name, 0, 2) . DIRECTORY_SEPARATOR . $name;
        $dir = dirname($filename);
        if (!is_dir($dir)) @mkdir($dir, 0755, true);

        return $filename;
    }

}